import {
  Injectable,
  ComponentRef,
  Renderer2,
  RendererFactory2,
  ComponentFactoryResolver,
  ApplicationRef,
  OnDestroy,
} from '@angular/core';
import { ComponentPortal, DomPortalOutlet } from '@angular/cdk/portal';
import { Overlay } from '@angular/cdk/overlay';
import { LvConfig } from '../core/config';
import { TypeUtils } from '../core/utils/api';
import { MessageContainerService } from './message.container.service';
import { MessageContainerComponent } from './message.container.component';
import { MessageOptions } from './interface';

const containerMap = new Map<string, MessageContainerComponent>();
const MESSAGE_DEFAULT_OPTIONS = {
  lvType: 'default',
  lvShowIcon: true,
  lvWidth: 360,
  lvDuration: 3000,
  lvKeepShowOnHover: true,
  lvShowCloseButton: false,
  lvRenderType: 'html',
  lvPosition: 'topCenter',
  lvOffset: { x: 0, y: 0 },
};

@Injectable({ providedIn: 'root' })
export class MessageService implements OnDestroy {
  public typeUtils = TypeUtils;
  private renderer: Renderer2;

  constructor(
    private overlay: Overlay,
    rendererFactory: RendererFactory2,
    private messageContainer: MessageContainerService,
    private cfr: ComponentFactoryResolver,
    private appRef: ApplicationRef,
  ) {
    this.renderer = rendererFactory.createRenderer(null, null);
  }

  ngOnDestroy(): void {
    containerMap.clear();
  }

  create(options: MessageOptions): void {
    const isSimple = this.typeUtils.isRealString(options.lvTitle) ? false : true;
    this._createMessage(options, isSimple);
  }

  info(content, options?: MessageOptions): void {
    const _options: MessageOptions = {
      ...options,
      ...{ lvType: 'info', lvTitle: '', lvContent: content },
    };
    this._createMessage(_options, true);
  }

  error(content, options?: MessageOptions): void {
    const _options: MessageOptions = {
      ...options,
      ...{ lvType: 'error', lvTitle: '', lvContent: content },
    };
    this._createMessage(_options, true);
  }

  success(content, options?: MessageOptions): void {
    const _options: MessageOptions = {
      ...options,
      ...{ lvType: 'success', lvTitle: '', lvContent: content },
    };
    this._createMessage(_options, true);
  }

  warning(content, options?: MessageOptions): void {
    const _options: MessageOptions = {
      ...options,
      ...{ lvType: 'warning', lvTitle: '', lvContent: content },
    };
    this._createMessage(_options, true);
  }

  update(id, options?: MessageOptions): void {
    let isSimple = null;
    if (options && 'lvTitle' in options) {
      isSimple = this.typeUtils.isRealString(options.lvTitle) ? false : true;
    }
    this._updateMessage(id, options, isSimple);
  }

  destroy(id?): void {
    const _id = id ? id : null;
    ['topLeft', 'topCenter', 'topRight'].forEach((item): void => {
      const containerName = `${item}MessageContainer`;
      const container = this._createContainer(containerName);
      if (_id) {
        container.remove(item, _id);
      } else {
        container.removeAll();
      }
    });
  }

  private _generateId(): string {
    const prefix = 'message-';
    const suffix = new Date().getTime();
    return prefix + suffix;
  }

  private _createMessage(options: MessageOptions, isSimple): { options: any; isSimple: any; id: string } {
    const result = {
      id: options.lvMessageKey || this._generateId(),
      isSimple,
      options: this._mergeOptions(options),
    };
    const containerName = `${result.options.lvPosition}MessageContainer`;
    const container = this._createContainer(containerName);

    container.create(result);
    return result;
  }

  private _updateMessage(id, options: MessageOptions, isSimple): void {
    const result = {
      id,
      isSimple,
      options,
    };

    ['topLeft', 'topCenter', 'topRight'].forEach((item): void => {
      this.messageContainer.messages[item].forEach((msg): void => {
        if (msg.id === id) {
          const containerName = `${item}MessageContainer`;
          const container = this._createContainer(containerName);
          container.update(item, result);
        }
      });
    });
  }

  private _mergeOptions(options): any {
    const globalOptions = LvConfig.messageOptions;
    let _options = {};
    if (typeof globalOptions.lvDuration === 'object') {
      if (options.lvType === 'info') {
        _options = { lvDuration: globalOptions.lvDuration[0] };
      }
      if (options.lvType === 'success') {
        _options = { lvDuration: globalOptions.lvDuration[1] };
      }
      if (options.lvType === 'warning') {
        _options = { lvDuration: globalOptions.lvDuration[2] };
      }
      if (options.lvType === 'error') {
        _options = { lvDuration: globalOptions.lvDuration[3] };
      }
    }

    return {
      ...MESSAGE_DEFAULT_OPTIONS,
      ...globalOptions,
      ..._options,
      ...options,
    };
  }

  private _createContainer(name): MessageContainerComponent {
    if (containerMap.has(name)) {
      return containerMap.get(name) as MessageContainerComponent;
    }

    let containerRef: ComponentRef<MessageContainerComponent> | null;
    // Create a message container
    const positionMap = {
      topLeftMessageContainer: 'lv-message-container-topLeft',
      topRightMessageContainer: 'lv-message-container-topRight',
      topCenterMessageContainer: 'lv-message-container-topCenter',
    };
    const containerEle = document.querySelector(`.${positionMap[name]}`);
    if (containerEle) {
      // 多个<lv-message-container>挂载在一个元素上
      const portalHost = new DomPortalOutlet(containerEle, this.cfr, this.appRef);
      containerRef = portalHost.attach(new ComponentPortal(MessageContainerComponent));
    } else {
      const overlayRef = this.overlay.create();
      containerRef = overlayRef.attach(new ComponentPortal(MessageContainerComponent));
      containerRef.changeDetectorRef.detectChanges(); // Manual change detection to avoid multiple detection errors

      // Set message container position
      this.renderer.addClass(overlayRef.overlayElement, 'lv-message-container');
      this.renderer.addClass(overlayRef.overlayElement, positionMap[name]);
    }

    // Save container map
    containerMap.set(name, containerRef.instance);

    return containerRef.instance;
  }
}
